﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Reflection;

namespace King.Extensions {

    public class Formatter : ICustomFormatter, IFormatProvider {

        private IFormatProvider m_nextProvider;
        private Func<string, string> m_escaper;

        public Formatter(IFormatProvider nextProvider = null, Func<string, string> escaper = null) {
            m_nextProvider = nextProvider;
            m_escaper = escaper;
        }

        protected virtual string Format(string format, object value) {
            return null;
        }

        public string Format(string format, object value, IFormatProvider formatProvider) {

            string result = null;

            // self formatting
            if (value is IFormattable)
                result = ((IFormattable)value).ToString(format, CultureInfo.CurrentCulture);

            // custom formatting
            if (result == null && format != null)
                result = Format(format, value);

            // chain providers
            if (result == null && m_nextProvider != null) {
                var formatter = (ICustomFormatter)m_nextProvider.GetFormat(typeof(ICustomFormatter));
                if (formatter != null)
                    result = formatter.Format(format, value, m_nextProvider);
            }

            // default self formatting
            if (result == null)
                result = value.ToString();

            // escape any formatted value
            if (m_escaper != null)
                result = m_escaper(result);

            return result;
        }
        public object GetFormat(Type formatType) {
            // boilerplate (see: http://msdn.microsoft.com/en-us/library/system.icustomformatter.aspx)

            if (formatType != typeof(ICustomFormatter))
                return null;

            return this;
        }
    }
    public sealed class VariableFormatter : Formatter {

        private Func<string, string> m_escaper;

        public VariableFormatter(IFormatProvider nextProvider = null, Func<string, string> escaper = null) 
            : base(nextProvider) {
            m_escaper = escaper;
        }

        protected override string Format(string format, object value) {

            var match = Regex.Match(format, "<%(?<type>:|=)(?<name>[^>]*)%>(?<format>.*)");
            if (!match.Success)
                return null;

            var variableType = match.Get("type");
            var variableName = match.Get("name");
            var variableFormat = "{0" + match.Get("format") + "}";

            var bf = BindingFlags.GetProperty | 
                BindingFlags.NonPublic | BindingFlags.Public |
                BindingFlags.Static | BindingFlags.Instance;

            var variableValue = value.GetType().InvokeMember(
                variableName, bf, Type.DefaultBinder, value, new object[] { });

            var formattedValue = string.Format(this, variableFormat, variableValue);

            // literal (no escaping)
            if (variableType == "=")
                return formattedValue;

            if (m_escaper == null)
                throw new FormatException("Unable to format '" + format + "' because no escaper was provided.");

            return m_escaper(formattedValue);
        }
    }
}
